#include "timedmultiset.h"

#include <bool.h>
#include <btime.h>
#include <number.h>
#include <typefactory.h>

#include <QtCore/QStringList>
#include <QtCore/QVariant>

using namespace core;
using namespace wfnet;

const TypeName TimedMultiset::NAME = Type::demangledName(
    typeid(TimedMultiset).name());
const ValueKeywordMask TimedMultiset::KEYWORD_MASK =
    "\\{(\\d+\\*%1(@\\d+){0,1}(\\s\\+\\s){0,1})+\\}";

bool timedMsSortHelper(const QPair<ColorSet, BasicTime>& p1,
    const QPair<ColorSet, BasicTime>& p2)
{
    return p1.second < p2.second;
}

TimedMultiset::TimedMultiset() :
    IMultiset(), Type(),
    m_colSetName(),
    m_values(),
    m_timeStamps(),
    m_name(NAME)
{
    fillOperations();
}

TimedMultiset::TimedMultiset(const ColorSetName& colSetName) :
    IMultiset(), Type(),
    m_colSetName(colSetName),
    m_values(),
    m_timeStamps(),
    m_name(QString("%1_%2").arg(NAME).arg(m_colSetName))
{
    fillOperations();
}

TimedMultiset::TimedMultiset(const ColorSetName& colSetName,
    const TypeValue& value) : IMultiset(), Type(),
        m_colSetName(colSetName),
        m_values(),
        m_name(QString("%1_%2").arg(NAME).arg(m_colSetName))
{
    fillOperations();

    parseValue(value);
}

TimedMultiset::TimedMultiset(const TimedMultiset& other) :
    IMultiset(), Type(),
    m_colSetName(other.m_colSetName),
    m_values(other.m_values),
    m_timeStamps(other.m_timeStamps),
    m_name(other.m_name)
{
    fillOperations();
}

TimedMultiset::TimedMultiset(const TimedMultiset& other,
    const TypeValue& value) :
        IMultiset(), Type(),
        m_colSetName(other.m_colSetName),
        m_values(),
        m_timeStamps(other.m_timeStamps),
        m_name(other.m_name)
{
    fillOperations();

    parseValue(value);
}

TimedMultiset::~TimedMultiset()
{

}

bool TimedMultiset::isTimed() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return true;
}

QString TimedMultiset::name() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_name;
}

bool TimedMultiset::matches(const TypeName& name) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    const Type* type =
        TypeFactory::getInstance()->getTypeInstance(m_colSetName);

    Q_ASSERT(type != 0);

    return type->name() == name || m_name == name || NAME == name;
}

bool TimedMultiset::matches(const Type& type) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    const TimedMultiset* other = dynamic_cast<const TimedMultiset* >(&type);
    const ColorSet* otherAsColorSet = dynamic_cast<const ColorSet* >(&type);
    return other != 0 ? m_name == other->m_name :
        otherAsColorSet != 0 ? otherAsColorSet->matches(m_colSetName) :
            false;
}

bool TimedMultiset::canConvert(const TypeValue& value) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    bool canConvert = true;

    const Type* type =
        TypeFactory::getInstance()->getTypeInstance(m_colSetName);

    Q_ASSERT(type != 0);

    QRegExp regex(keywordMask().arg(type->keywordMask()));
    bool canParse = regex.exactMatch(value);

    if (canParse)
    {
        if (!value.contains("@"))
        {
            canConvert = false;
        }
        else
        {
            regex.setPattern("(\\{|\\})");
            QString strippedValue = value;
            strippedValue = strippedValue.remove(regex);

            regex.setPattern("\\s\\+\\s");
            const QStringList values = strippedValue.split(regex,
                QString::SkipEmptyParts);

            regex.setPattern("@\\d+");

            foreach (const QString& _value, values)
            {
                if (_value.contains("@"))
                {
                    const QStringList _valueSplit = _value.split("*",
                        QString::SkipEmptyParts);

                    TypeValue parsedValue = _valueSplit[1];
                    parsedValue = parsedValue.remove(regex);

                    if (!type->canConvert(parsedValue))
                    {
                        canConvert = false;
                        break;
                    }
                }
                else
                {
                    canConvert = false;
                    break;
                }
            }
        }
    }
    else
    {
        canConvert = false;
    }

    return canConvert;
}

ValueKeywordMask TimedMultiset::keywordMask() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return KEYWORD_MASK;
}

bool TimedMultiset::hasIdentity() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return true;
}

void TimedMultiset::setIdentity(const Type& identity)
{
    const TimedMultiset* other = dynamic_cast<const TimedMultiset* >(
        &identity);

    if (other != 0 && m_colSetName.isEmpty())
    {
        m_colSetName = other->m_colSetName;
        m_name = constructName(m_colSetName);
    }
}

Type* TimedMultiset::copy() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    TimedMultiset* newInstance = new TimedMultiset(*this);
    return newInstance;
}

TypeValue TimedMultiset::toValue() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    TypeValue value = "{";

    foreach (const CombinedKey& key, m_values.keys())
    {
        const uint coefficient = m_values[key];

        value.append(QString("%1*%2@%3 + ").arg(coefficient).arg(
            key.first.getValue().toString()).arg(key.second.getTime()));
    }

    value.remove(value.length() - 3, 3);
    value.append("}");

    return value;
}

bool TimedMultiset::isEmpty() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_values.isEmpty();
}

uint TimedMultiset::size() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_values.size();
}

bool TimedMultiset::contains(const ColorSet& value,
    const BasicTime& time) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    CombinedKey key(value, time);
    return m_values.contains(key);
}

uint TimedMultiset::coefficient(const ColorSet& value,
    const BasicTime& time) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    CombinedKey key(value, time);
    return m_values.contains(key) ? m_values[key] : 0;
}

void TimedMultiset::insert(const ColorSet& value,
    const BasicTime& time)
{
    Q_ASSERT(!m_colSetName.isEmpty());

    if (m_colSetName == value.name() && value.isTimed())
    {
        CombinedKey key(value, time);
        if (m_values.contains(key))
        {
            ++m_values[key];
        }
        else
        {
            m_values[key] = 1;
        }

        TimeStampList& timeStamps = m_timeStamps[value];
        timeStamps.append(time);
        qSort(timeStamps.begin(), timeStamps.end());
    }
    else
    {
        Q_ASSERT(false);
    }
}

void TimedMultiset::remove(const ColorSet& value,
    const BasicTime& time)
{
    Q_ASSERT(!m_colSetName.isEmpty());

    if (m_colSetName == value.name() && value.isTimed())
    {
        CombinedKey key(value, time);
        if (m_values.contains(key))
        {
            uint& count = m_values[key];
            if (count == 1)
            {
                m_values.remove(key);
                m_timeStamps.remove(value);
            }
            else
            {
                --count;

                TimeStampList& timeStamps = m_timeStamps[value];
                timeStamps.removeOne(time);
            }
        }
    }
    else
    {
        Q_ASSERT(false);
    }
}

void TimedMultiset::clear()
{
    Q_ASSERT(!m_colSetName.isEmpty());

    m_values.clear();
    m_timeStamps.clear();
}

ColorSet TimedMultiset::at(uint index) const
{
    Q_ASSERT(!m_colSetName.isEmpty() && index < (uint)m_values.size());

    return m_values.keys().at(index).first;
}

TimedMultiset TimedMultiset::intersect(const TimedMultiset& other) const
{
    TimedMultiset tmt(m_colSetName);

    const QSet<CombinedKey> combinedKeys =
        m_values.keys().toSet();

    const QSet<CombinedKey> otherCombinedKeys =
        other.m_values.keys().toSet();

    QSet<ColorSet> simpleKeys;
    QSet<ColorSet> otherSimpleKeys;

    foreach (const CombinedKey& key, combinedKeys)
    {
        simpleKeys << key.first;
    }

    foreach (const CombinedKey& key, otherCombinedKeys)
    {
        otherSimpleKeys << key.first;
    }

    QSet<ColorSet> intersectedSimpleKeys = simpleKeys.intersect(
        otherSimpleKeys);

    QSet<CombinedKey> intersectedKeys;

    foreach (const ColorSet& color, intersectedSimpleKeys)
    {
        const BasicTime time = m_timeStamps[color].last() <
            other.m_timeStamps[color].last() ? other.m_timeStamps[
                color].last() : m_timeStamps[color].last();
        intersectedKeys << CombinedKey(color, time);
    }

    foreach (const CombinedKey& key, intersectedKeys)
    {
        const uint firstCoefficient = m_values.contains(key) ? m_values[key] :
            0;
        const uint secondCoefficient = other.m_values.contains(key) ?
            other.m_values[key] : 0;
        const uint maxIntrCoefficient =
            firstCoefficient > secondCoefficient ?
                secondCoefficient : firstCoefficient;

        tmt.m_values[key] = maxIntrCoefficient;
    }

    return tmt;
}

TimedMultiset::TimeStampList TimedMultiset::getTimeStamps(
    const ColorSet& value) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    if (m_timeStamps.contains(value))
    {
        return m_timeStamps[value];
    }
    else
    {
        return TimeStampList();
    }
}

QList<QPair<ColorSet, BasicTime> > TimedMultiset::getSortedLowerValueList(
    core::BasicTime lowerThan) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    QList<QPair<ColorSet, BasicTime> > ret;

    QList<QPair<ColorSet, BasicTime> > keys = m_values.keys();
    qSort(keys.begin(), keys.end(), timedMsSortHelper);

    const ColorSet* color = dynamic_cast<const ColorSet* >(
        TypeFactory::getInstance()->getTypeInstance(m_colSetName));

    QPair<ColorSet, BasicTime> value(*color, lowerThan);

    QList<QPair<ColorSet, BasicTime> >::iterator lowerEnd =
        qLowerBound(keys.begin(), keys.end(), value, timedMsSortHelper);

    if (lowerEnd != keys.begin())
    {
        QList<QPair<ColorSet, BasicTime> >::iterator keysBegin = keys.begin();
        for (; keysBegin != keys.end(); ++keysBegin)
        {
            if (keysBegin->second <= lowerThan)
            {
                ret.append(*keysBegin);
            }
            else
            {
                break;
            }
        }
    }

    return ret;
}

QList<QPair<ColorSet, BasicTime> > TimedMultiset::getSortedHigherValueList(
    core::BasicTime higherThan) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    QList<QPair<ColorSet, BasicTime> > ret;

    QList<QPair<ColorSet, BasicTime> > keys = m_values.keys();
    qSort(keys.begin(), keys.end(), timedMsSortHelper);

    const ColorSet* color = dynamic_cast<const ColorSet* >(
        TypeFactory::getInstance()->getTypeInstance(m_colSetName));

    QPair<ColorSet, BasicTime> value(*color, higherThan);

    QList<QPair<ColorSet, BasicTime> >::iterator higherEnd =
        qUpperBound(keys.begin(), keys.end(), value, timedMsSortHelper);

    if (higherEnd != keys.end())
    {
        QList<QPair<ColorSet, BasicTime> >::iterator keysEnd = keys.end();
        for (; higherEnd != keysEnd; ++higherEnd)
        {
            ret.append(*higherEnd);
        }
    }

    return ret;
}

bool TimedMultiset::getUpperBoundValue(ColorSet& bound, BasicTime& time,
    core::BasicTime of) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    bool ret = false;

    QList<QPair<ColorSet, BasicTime> > keys = m_values.keys();
    qSort(keys.begin(), keys.end(), timedMsSortHelper);

    const ColorSet* color = dynamic_cast<const ColorSet* >(
        TypeFactory::getInstance()->getTypeInstance(m_colSetName));

    QPair<ColorSet, BasicTime> value(*color, of);

    QList<QPair<ColorSet, BasicTime> >::iterator upperBound =
        qUpperBound(keys.begin(), keys.end(), value, timedMsSortHelper);

    if (upperBound != keys.end())
    {
        bound = upperBound->first;
        time = upperBound->second;
        ret = true;
    }

    return ret;
}

TimedMultiset TimedMultiset::operator+(const TimedMultiset& other) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    TimedMultiset ret = *this;

    if (m_colSetName == other.m_colSetName)
    {
        ConstPrivateIterator itOther = other.m_values.constBegin();

        while (itOther != other.m_values.constEnd())
        {
            if (ret.m_values.find(itOther.key()) != ret.m_values.constEnd())
            {
                ret.m_values[itOther.key()] += itOther.value();
            }
            else
            {
                ret.m_values[itOther.key()] = 0;
                ret.m_values[itOther.key()] += itOther.value();
            }

            TimeStampList& timeStamps = ret.m_timeStamps[itOther.key().first];
            timeStamps += other.m_timeStamps[itOther.key().first];

            qSort(timeStamps.begin(), timeStamps.end());

            ++itOther;
        }
    }
    else
    {
        Q_ASSERT(false);
    }

    return ret;
}

TimedMultiset TimedMultiset::operator+(const BasicTime& time) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    TimedMultiset ret = *this;

    TIterator tIt = ret.m_timeStamps.begin();

    while (tIt != ret.m_timeStamps.end())
    {
        TimeStampList& timeStamps = tIt.value();

        for (int i = 0; i < timeStamps.size(); ++i)
        {
            timeStamps[i].add(time.getTime());
        }

        ++tIt;
    }

    return ret;
}

TimedMultiset TimedMultiset::operator-(const TimedMultiset& other) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    TimedMultiset ret(this->m_colSetName);

    if (m_colSetName == other.m_colSetName)
    {
        ConstPrivateIterator itOther = other.m_values.constBegin();

        if (other <= *this)
        {
            ret = *this;

            while (itOther != other.m_values.constEnd())
            {
                ret.m_values[itOther.key()] -= itOther.value();

                if (ret.m_values[itOther.key()] != 0)
                {
                    TimeStampList& timeStamps = ret.m_timeStamps[
                        itOther.key().first];

                    const TimeStampList& otherTimeStamps =
                        other.m_timeStamps[itOther.key().first];

                    const int size = otherTimeStamps.size();

                    for (int i = 0; i < size; ++i)
                    {
                        TimeStampList::const_iterator tIt = qUpperBound(
                            timeStamps, otherTimeStamps.at(i));
                        if (tIt == timeStamps.constEnd())
                        {
                            --tIt;
                        }
                        timeStamps.removeOne(*tIt);
                    }
                }
                else
                {
                    ret.m_values.remove(itOther.key());
                    ret.m_timeStamps[itOther.key().first].removeOne(
                        itOther.key().second);
                }

                ++itOther;
            }
        }
    }
    else
    {
        Q_ASSERT(false);
    }

    return ret;
}

TimedMultiset TimedMultiset::operator*(uint scalar) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    TimedMultiset ret = *this;

    PrivateIterator itRet = ret.m_values.begin();

    while(itRet != ret.m_values.end())
    {
        itRet.value() *= scalar;
        ++itRet;
    }

    return ret;
}

bool TimedMultiset::operator<=(const TimedMultiset& other) const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    bool ret = true;

    if (m_colSetName == other.m_colSetName)
    {
        ConstPrivateIterator itThis = m_values.constBegin();

        while (itThis != m_values.constEnd())
        {
            if (coefficient(itThis.key().first) > other.coefficient(
                itThis.key().first))
            {
                ret = false;
                break;
            }
            ++itThis;
        }

        if (ret)
        {
            TConstIterator tItThis = m_timeStamps.constBegin();

            while (tItThis != m_timeStamps.constEnd())
            {
                const TimeStampList& timeStamps = tItThis.value();
                const int size = timeStamps.size();

                const TimeStampList& otherTimeStamps = other.m_timeStamps[
                    tItThis.key()];

                if (size > otherTimeStamps.size())
                {
                    ret = false;
                    break;
                }

                for (int i = 0; i < size; ++i)
                {
                    if (timeStamps.at(i) < otherTimeStamps.at(i))
                    {
                        ret = false;
                        break;
                    }
                }

                ++tItThis;
            }
        }
    }
    else
    {
        Q_ASSERT(false);
    }

    return ret;
}

TypeName TimedMultiset::typeOf() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    return m_colSetName;
}

Multiset TimedMultiset::toMultiset() const
{
    Q_ASSERT(!m_colSetName.isEmpty());

    Multiset mt(m_colSetName);

    ConstPrivateIterator it = m_values.constBegin();

    for (; it != m_values.constEnd(); ++it)
    {
        for (uint i  = 0; i < it.value(); ++i)
        {
            mt.insert(it.key().first);
        }
    }

    return mt;
}

TimedMultiset::ConstIterator TimedMultiset::constBegin() const
{
    return m_values.keys().toSet().constBegin();
}

TimedMultiset::ConstIterator TimedMultiset::constEnd() const
{
    return m_values.keys().toSet().constEnd();
}

TypeName TimedMultiset::constructName(const ColorSetName& name)
{
    return QString("%1_%2").arg(NAME).arg(name);
}

void TimedMultiset::parseValue(const TypeValue& value)
{
    if (canConvert(value))
    {
        const ColorSet* type = dynamic_cast<const ColorSet* >(
            TypeFactory::getInstance()->getTypeInstance(m_colSetName));

        Q_ASSERT(type != 0);

        QRegExp regex("(\\{|\\})");
        QString strippedValue = value;
        strippedValue = strippedValue.remove(regex);

        regex.setPattern("\\s\\+\\s");
        const QStringList values = strippedValue.split(regex,
            QString::SkipEmptyParts);

        foreach (const QString& _value, values)
        {
            const QStringList _valueSplit = _value.split("*",
                QString::SkipEmptyParts);
            const QStringList _valueAndTimeSplit = _valueSplit[1].split("@",
                QString::SkipEmptyParts);

            const int coefficient = _valueSplit[0].toInt();
            const uint time = _valueAndTimeSplit[1].toUInt();

            for (int i = 0; i < coefficient; ++i)
            {
                ColorSet color = *type;
                color.setValue(QVariant(_valueAndTimeSplit[0]));
                insert(color, time);
            }
        }
    }
}

void TimedMultiset::fillOperations()
{
    //m_operations.insert(OPERATION_EQUALS, OP(Bool::NAME));
    //m_operations.insert(OPERATION_NEQUALS, OP(Bool::NAME));
    m_operations.insert(OPERATION_LEQUALTO, OP(Bool::NAME));

    m_operations.insert(OPERATION_ADDITION, OP(NAME));
    m_operations.insert(OPERATION_SUBSTRACTION, OP(NAME));
    m_operations.insert(OPERATION_MULTIPLICATION, OP2(NAME, Number::NAME));
}

uint TimedMultiset::coefficient(const ColorSet& value) const
{
    uint ret = 0;

    ConstPrivateIterator itThis = m_values.constBegin();

    while (itThis != m_values.constEnd())
    {
        if (itThis.key().first == value)
        {
            ret += itThis.value();
        }

        ++itThis;
    }

    return ret;
}
